﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IMLibrary.ExZeroMQ;
using IMLibrary.Server;
using IMLibrary.WS.Net;
using IMLibrary.WS.Server;
using IMLibrary.WS;

using IMLibrary.BinaryPacket;
using IMLibrary.Security; 
using P;
using Ourmsg.Enum;
using Ourmsg.Factory;
using Ourmsg.Server.MemoryObject;

namespace Ourmsg.Server
{
    #region 用户登录代理服务器
    /// <summary>
    /// 用户登录代理服务器
    /// </summary>
    public class AgentServer : WebSocketService
    {
        #region 构造
        /// <summary>
        /// 
        /// </summary>
        /// <param name="routerIP">路由服务器IP</param>
        /// <param name="routerPort">路由服务器端口</param>
        /// <param name="serverID">服务器ID，唯一标识</param>
        /// <param name="agentPort">客户端登录端口号</param>
        /// <param name="p2pPort"></param>
        /// <param name="imageFilePort"></param>
        /// <param name="offlineFilePort"></param>
        public AgentServer(string routerIP, int routerPort, string serverID, int agentPort,
             int p2pPort,int imageFilePort,int  offlineFilePort)
        {
            RouterIP = routerIP;
            RouterPort = routerPort;
            ServerID = serverID;
            AgentPort = agentPort;
            P2pPort = p2pPort;
            ImageFilePort = imageFilePort;
            OfflineFilePort = offlineFilePort;
            Path = "/Chats";
        }
        #endregion

        #region 字段 
        /// <summary>
        /// PC客户端登录用户集合
        /// </summary>
        Dictionary<UInt32, AgentUser> PC_Users = new Dictionary<uint, AgentUser>();
        /// <summary>
        /// APP客户端登录用户集合
        /// </summary>
        Dictionary<UInt32, AgentUser> APP_Users = new Dictionary<uint, AgentUser>();
        /// <summary>
        /// Mq客户端
        /// </summary>
        MQClient MqClient;
        /// <summary>
        /// 离线消息存取客户端
        /// </summary>
        OfflineMessageClient offlineMsgClient;
        #endregion

        #region 属性
        /// <summary>
        /// 服务器ID
        /// </summary>
        public string ServerID
        { private set; get; }
        public string RouterIP
        { private set; get; }
        public int RouterPort
        { private set; get; }
        public int AgentPort
        { set; get; }
        public int P2pPort
        { set; get; } 
        public int ImageFilePort
        { set; get; }
        public int OfflineFilePort
        { set; get; }
        #endregion

        #region 私有方法 
        private void MqClient_Receive(object sender, MQEventArgs e)
        {
            if (e.Data.Length < BinaryMsg.HeaderLength) return;//非法消息退出
            if (WSServer != null)
            {
                BinaryMsg msg = new BinaryMsg(e.Data);
                SendMessageToUser(msg.To, msg);
            }
        }
        #endregion

        #region 服务功能
        public void Start()
        {
            if (MqClient == null)
            {
                MqClient = new MQClient();
                MqClient.Receive += MqClient_Receive;
                MqClient.Connect(RouterIP, RouterPort, ClientType.Dealer, ServerID);
                MqClient.StartAsyncReceive();
            }
            if (WSServer == null)
            {
                WSServer = new WebSocketServer(AgentPort);
                WSServer.KeepClean = true;
                WSServer.AddWebSocketService<WebSocketSession>(Path, () => new WebSocketSession(this)); //
                WSServer.AuthenticationSchemes = AuthenticationSchemes.Basic;
                WSServer.Realm = "OurMsg Server";
                WSServer.UserCredentialsFinder = ID =>
                {
                    var name = ID.Name;
                    // Return user name, password, and roles.If the user credentials aren't found,null.
                    return name == "Ourmsg" ? new NetworkCredential(name, "123456@Ourmsg.net") : null;
                };
                WSServer.Start();
            }
            if (offlineMsgClient == null)
            {
                offlineMsgClient = new OfflineMessageClient();
                offlineMsgClient.OpenConnection();//连接离线存取消息服务器
            }
        }

        public void Stop()
        {
            if (WSServer != null)
            {
                WSServer.Stop();
                WSServer = null;
            }
            if (MqClient != null)
            {
                MqClient.Receive -= MqClient_Receive;
                MqClient.Dispose();
                MqClient = null;
            }
            if (offlineMsgClient != null)
            {
                offlineMsgClient.CloseConnection();
                offlineMsgClient = null;
            }

            #region  清空缓存在内存中的用户信息
            {
                lock (PC_Users)
                PC_Users.Clear();
                lock (APP_Users)
                APP_Users.Clear();
            }
            #endregion
        }
        #endregion

        #region 联系客户端Session事件

        #region 其他事件
        public override void OnClose(WebSocketSession session, CloseEventArgs e)
        {
            var user = session.Tag as AgentUser;
            var i = DBO.SetUserOffline(user.UserID);

            SendUserStatus(user, ShowType.Offline);

            Console.WriteLine("SetUserOffline:" + i);

            if (user != null && user.IsAPP)
                lock (APP_Users)
                {
                    var t = APP_Users.Remove(user.UserID);
                    Console.WriteLine(session.ID + ":is DisConnection!user delete:" + t.ToString());
                }
            else if (user != null)
                lock (PC_Users)
                {
                    var t = PC_Users.Remove(user.UserID);
                    Console.WriteLine(session.ID + ":is DisConnection!user delete:" + t.ToString());
                }
        }

        public override void OnOpen(WebSocketSession chat)
        {
            Console.WriteLine(chat.ID + ":is Connection!");
        }

        public override void OnError(WebSocketSession chat, ErrorEventArgs e)
        {
            Console.WriteLine("error:" + e.Message);
        }

        private void CloseSession(string ID)
        {

            Sessions.CloseSession(ID);//强行关闭连接
        }
        #endregion

        public override void OnMessage(WebSocketSession session, MessageEventArgs e)
        {
            #region 初始化消息协议包
            //Console.WriteLine("收到包");
            if (e.RawData.Length < BinaryMsg.HeaderLength)
                return;//如果收到非法包，则退出  
            var msg = new BinaryMsg(e.RawData);
            msg.IsSave = false;//标注消息不需要存为离线消息
            #endregion

            #region 登录认证
            if (msg.CommandType == (byte)CommandType.Auth)
            {
                if (msg.Encrypt)//先解密 
                    msg.Payload = Encryption.Decrypt3DES(msg.Payload);
                if (msg.Compress)//再解压
                    msg.Payload = IMLibrary.GZip.Stream.Decompress(msg.Payload);

                var auth = XML.ConvertToObject(msg.GetPayloadString()) as Auth;
                if (auth == null)
                {
                    CloseSession(session.ID);//非法攻击，一律强行关闭连接
                    return;
                }

                var DBAuth = DBO.GetUserAuth(auth.AuthID);
                if (DBAuth != null)//如果用户不存在
                {
                    if (DBAuth.Password != auth.Password)
                    {
                        msg.CommandType = (byte)CommandType.AuthError;//告之用户密码错误
                        msg.Encrypt = false;
                        msg.Compress = false;
                        SendMessageToSession(session.ID, msg.Header);
                        System.Threading.Thread.Sleep(1000);
                        CloseSession(session.ID);//强行关闭连接
                        return;
                    }

                    UInt32 userID = DBAuth.UserID;
                    AgentUser user;//用户
                    var ip = session.Context.UserEndPoint.Address.ToString();

                    #region 设置别处登录包
                    msg.CommandType = (byte)CommandType.ElseLogin;//告之用户别处登录
                    msg.Encrypt = false;
                    msg.Compress = false;
                    msg.To = userID;
                    #endregion

                    #region 判断用户是PC还是APP客户端登录
                    if (msg.APPClient)//如果是APP客户端
                    {
                        if (APP_Users.TryGetValue(userID, out user))//如果APP客户端登录到本服务器，则通知其下线
                        {
                            SendMessageToSession(user.SessionID, msg.Header);
                            CloseSession(user.SessionID);//强制下线  
                        }
                        else//如果APP客户端用户登录到其他服务器
                        {
                            MqClient.Send(msg.Header);//发送消息到路由器，如果用户在线则通知用户下线 
                            user = new AgentUser();
                            APP_Users.Add(userID, user);
                        }
                    }
                    else
                    {
                        if (PC_Users.TryGetValue(userID, out user))//如果PC客户端已经登录，则通知其下线
                        {
                            SendMessageToSession(user.SessionID, msg.Header);
                            CloseSession(user.SessionID);//强制下线  
                        }
                        else
                        {
                            MqClient.Send(msg.Header);//发送消息到路由器，如果用户在线则通知用户下线
                            user = new AgentUser();
                            PC_Users.Add(userID, user);
                        }
                    }
                    //更新登录信息

                    user.SessionID = session.ID;
                    user.UserID = userID;
                    user.PassowrdKey = Hasher.GetMD5Bytes(DBAuth.Password);//加密密钥
                    user.Encrypt = auth.Encrypt;
                    user.Compress = auth.Compress;
                    user.IsAPP = msg.APPClient;
                    session.Tag = user;
                    user.ShowType = auth.ShowType;
                    user.UserName = DBAuth.UserName;
                    user.HeadID = DBAuth.HeadID;
                    user.SysUser = DBAuth.SysUser;
                    var dynPassword = new Random().Next(100000, 100000000).ToString();
                    DBO.UpdateLoginInfo(userID, dynPassword, ip, auth.ShowType);
                    #endregion

                    #region 设置登录成功包
                    DBAuth.ShowType = auth.ShowType;
                    DBAuth.Password = "";
                    DBAuth.DynPassword = dynPassword;//返回一个动态密码，用于上传图片，文件等安全动态认证
                    DBAuth.P2pPort = P2pPort;//服务器提供的P2P服务端口
                    DBAuth.OfflineFilePort = OfflineFilePort;//服务器提供的离线文件服务端口
                    DBAuth.ImageFilePort = ImageFilePort;//服务器提供的离线图片文件服务端口
                    msg.CommandType = (byte)CommandType.Auth;//告之用户别处登录
                    msg.Payload = XML.ConvertToArray(DBAuth);
                    SendMessageToUser(user, msg);
                    #endregion

                    #region 发送好友分组
                    var groups = DBO.GetGroups(userID);
                    if (groups.Count > 0)
                        SendMessageToUser(userID, CommandType.GroupData, new Collection(groups));
                    #endregion

                    #region 发送群分组
                    var roomGroups = DBO.GetRoomGroups(userID);
                    if (roomGroups.Count > 0)
                        SendMessageToUser(userID, CommandType.RoomGroups, new Collection(roomGroups));
                    #endregion

                    #region 发送好友信息 
                    var friendsInfos = DBO.GetFriendBaseInfos(userID);
                    if (friendsInfos.Count > 0)
                        SendMessageToUser(userID, CommandType.FriendBaseData, new Collection(friendsInfos));
                    #endregion

                    #region 发送群信息 
                    var rooms = DBO.GetRoomBaseInfos(userID);
                    if (rooms.Count > 0)
                        SendMessageToUser(userID, CommandType.RoomBaseData, new Collection(rooms));
                    #endregion

                    #region 将上线信息通知到消息路由器，以及好友和群 
                    SendUserLoginedStatus(DBAuth, user.ShowType, user.IsAPP);
                    #endregion

                    #region 发送离线文件 
                    var offlineMsgs = offlineMsgClient.GetOfflineMessages(user.UserID);
                    if (offlineMsgs != null)
                        foreach (var offlineMsg in offlineMsgs)
                            SendMessageToUser(userID, new BinaryMsg(offlineMsg.Value));
                    #endregion
                }
            }
            #endregion

            #region 其他功能
            else
            {
                var user = session.Tag as AgentUser;
                if (user.UserID != msg.From) return; //如果发送的消息为假消息

                if (user == null)//如果用户未经登录认证，则强行关闭非法入侵用户
                    CloseSession(session.ID);
                if (msg.Encrypt && msg.PayloadLength > 0)//先解密 
                    msg.Payload = Encryption.Decrypt3DES(msg.Payload, user.PassowrdKey);
                if (msg.Compress && msg.PayloadLength > 0)//再解压
                    msg.Payload = IMLibrary.GZip.Stream.Decompress(msg.Payload);

                switch (msg.CommandType)
                {
                    #region 用户在线状态发生改变
                    case (byte)CommandType.Presence:
                        {
                            var prencens = XML.ConvertToObject(msg.GetPayloadString()) as Presence;
                            if (prencens == null) return;
                            if (user.ShowType != prencens.ShowType && prencens.ShowType != ShowType.Offline)
                            {
                                user.ShowType = prencens.ShowType;
                                SendUserStatus(user, prencens.ShowType);
                            }
                        }
                        break;
                    #endregion

                    case (byte)CommandType.RoomMessage:
                    case (byte)CommandType.UserMessage:
                        var message = XML.ConvertToObject(msg.GetPayloadString()) as Element;
                        if (message == null)
                            return;
                        message.From = user.UserID;
                        message.FromName = user.UserName;
                        message.FromHeadID = user.HeadID;
                        message.DateTime = DateTime.Now.ToString();
                        msg.Payload = XML.ConvertToArray(message);
                        //msg.IsSave = true;//标注消息需要存为离线消息
                        MqClient.Send(msg.ToArray());
                        break;
                    case (byte)CommandType.ResponsesAddRoom:
                    case (byte)CommandType.ResponsesInviteAddRoom:
                    case (byte)CommandType.ResponsesAddFriend:
                    case (byte)CommandType.CreateRoom:
                    case (byte)CommandType.DelRoomUser:
                    case (byte)CommandType.DelFriend:
                    case (byte)CommandType.QuitRoom : 
                    case (byte)CommandType.SetRoomBaseInfo:
                    case (byte)CommandType.SetRoomUserCard:
                    case (byte)CommandType.SetRoomManger:
                        MqClient.Send(msg.ToArray());
                        break;

                    #region  获取群用户数据（包含在线状态）
                    case (byte)CommandType.GetRoomUserData:
                        var roomUsers = DBO.GetRoomUsers(msg.To);
                        if (roomUsers.Count > 0)
                        {
                            msg.Payload = XML.ConvertToArray(new Collection(roomUsers));
                            SendMessageToUser(user, msg);
                        }
                        break;
                    #endregion

                    #region 查询好友和群
                    case (byte)CommandType.Query://查询满足条件的用户或群
                        {
                            #region 
                            Query q = XML.ConvertToObject(msg.GetPayloadString()) as Query;
                            if (q == null) return;
                            if (q.QueryClass == QueryClass.User)//如果查询用户
                            {
                                var qUsers = DBO.QueryUsers(q);
                                if (qUsers.Count > 0)
                                {
                                    QueryResult qr = new QueryResult();
                                    qr.Results = new List<object>(qUsers);
                                    msg.Payload = XML.ConvertToArray(qr);
                                    SendMessageToUser(user, msg);
                                }
                            }
                            else if (q.QueryClass == QueryClass.Room)//如果查询群
                            {
                                var qRooms = DBO.QueryRooms(q);
                                if (qRooms.Count > 0)
                                {
                                    QueryResult qr = new QueryResult();
                                    qr.Results = new List<object>(qRooms);
                                    msg.Payload = XML.ConvertToArray(qr);
                                    SendMessageToUser(user, msg);
                                }
                            }
                            #endregion
                        }
                        break;
                    #endregion

                    #region 请求添加好友 
                    case (byte)CommandType.RequestAddFriend:
                        {
                            InviteMsg inviteMsg = XML.ConvertToObject(msg.GetPayloadString()) as InviteMsg;
                            if (inviteMsg == null) return;
                            if (msg.To == 0) return;
                            inviteMsg.UserID = user.UserID;
                            inviteMsg.UserName = user.UserName;
                            inviteMsg.HeadID = user.HeadID;
                            msg.Payload = XML.ConvertToArray(inviteMsg);
                            //msg.IsSave = true;//标注消息需要存为离线消息
                            MqClient.Send(msg.ToArray());
                        }
                        break;
                    #endregion

                    #region 请求/邀请用户添加群
                    case (byte)CommandType.RequestAddRoom:
                    case (byte)CommandType.InviteAddRoom:
                        {
                            InviteMsg inviteMsg = XML.ConvertToObject(msg.GetPayloadString()) as InviteMsg;
                            if (inviteMsg == null) return;
                            if (msg.To != 0)
                            {
                                inviteMsg.UserID = user.UserID;
                                inviteMsg.UserName = user.UserName;
                                inviteMsg.HeadID = user.HeadID;
                                msg.Payload = XML.ConvertToArray(inviteMsg);
                                //msg.IsSave = true;//标注消息需要存为离线消息
                                MqClient.Send(msg.ToArray());
                            }
                        }
                        break;
                        #endregion
                }
            }
            #endregion 
        }

        #endregion 

        #region 发送消息
        /// <summary>
        /// 
        /// </summary>
        /// <param name="userID"></param>
        /// <param name="obj"></param>
        public void SendMessageToUser(UInt32 userID, CommandType cmdType, object obj)
        {
            var data = XML.ConvertToArray(obj);
            BinaryMsg msg = new BinaryMsg();
            msg.CommandType = (byte)cmdType;
            msg.Payload = data;
            SendMessageToUser(userID, msg);
        }

        /// <summary>
        /// 发送消息给用户
        /// </summary>
        /// <param name="userID"></param>
        /// <param name="msg"></param>
        public void SendMessageToUser(UInt32 userID, BinaryMsg msg)
        {
            AgentUser user;
            if (PC_Users.TryGetValue(userID, out user))
                SendMessageToUser(user, msg);//发送消息到PC客户端 
            if (APP_Users.TryGetValue(userID, out user))
                SendMessageToUser(user, msg);//发送消息到APP客户端
        }


        /// <summary>
        /// 广播消息
        /// </summary>
        /// <param name="msg"></param>
        public void Broadcast(BinaryMsg msg)
        {
            foreach (var user in PC_Users.Values.ToArray())
                SendMessageToUser(user, msg);
            foreach (var user in APP_Users.Values.ToArray())
                SendMessageToUser(user, msg);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="user"></param>
        /// <param name="msg"></param>
        public void SendMessageToUser(AgentUser user, BinaryMsg msg)
        {
            msg.Compress = false;
            msg.Encrypt = false;

            if (msg != null && msg.Payload != null && msg.PayloadLength > 0)
            {
                if (user.Compress && msg.PayloadLength > 512)//先压缩
                {
                    msg.Payload = IMLibrary.GZip.Stream.Compress(msg.Payload);
                    msg.Compress = true;
                }
                if (user.Encrypt)//再加密
                {
                    msg.Payload = IMLibrary.Security.Encryption.Encrypt3DES(msg.Payload, user.PassowrdKey);
                    msg.Encrypt = true;
                }
            }
            SendMessageToSession(user.SessionID, msg.ToArray());//发送消息到PC客户端  
        }

        /// <summary>
        /// 发送消息到SocketSessionID
        /// </summary>
        /// <param name="sessionID"></param>
        /// <param name="msg"></param>
        public void SendMessageToSession(string sessionID, byte[] data)
        {
            if (Sessions != null && data != null && data.Length > 0)
                Sessions.SendToAsync(data, sessionID, null);
        }
        #endregion
         
        #region 发送用户在线状态  
        /// <summary>
        /// 发送用户在线状态
        /// </summary>
        /// <param name="userID">用户帐号</param>
        /// <param name="appClient">是否客户端APP上线</param>
        /// <param name="showType">在线类型</param>UInt32 userID,bool appClient,
        private void SendUserStatus(AgentUser user, ShowType showType)
        {
            var p = new Presence();
            p.ShowType = showType;
            p.UserID = user.UserID;
            p.SysUser = user.SysUser;

            #region 组合用户好友帐号 
            //List<RouterCacheFriend> Friends;
            //if (UserFriends.TryGetValue(user.UserID, out Friends))
            //    foreach (var f in Friends )
            //        us.Friends += f.FriendID.ToString() + ",";
            #endregion

            #region 组合用户群帐号
            //List<UserRoom> Rooms;
            //if (UserRooms.TryGetValue(user.UserID, out Rooms))
            //    foreach (var r in Rooms)
            //        us.Rooms  += r.RoomID.ToString() + ",";
            #endregion

            BinaryMsg msg = new BinaryMsg((byte)CommandType.Presence);
            msg.APPClient = user.IsAPP;
            msg.SetPayloadString(XML.ConvertToString(p));
            MqClient.Send(msg.ToArray());
        }

        /// <summary>
        /// 发送成功登录的用户状态到路由器
        /// </summary>
        /// <param name="auth"></param>
        /// <param name="showType"></param>
        /// <param name="isAPP"></param>
        private void SendUserLoginedStatus(Auth auth, ShowType showType, bool isAPP)
        {
            var p = new Status();
            p.ShowType = showType;
            p.UserID = auth.UserID;
            p.SysUser = auth.SysUser;
            p.UserName = auth.UserName;
            p.HeadID = auth.HeadID;
            var addCondition = AddCondition.AllowAll;
            if (auth.AddCondition == 1)
                addCondition = AddCondition.NeedAuditing;
            else if (auth.AddCondition == 2)
                addCondition = AddCondition.RefuseALL;
            p.AddCondition = addCondition;

            BinaryMsg msg = new BinaryMsg((byte)CommandType.UserLogined);
            msg.APPClient = isAPP;
            msg.SetPayloadString(XML.ConvertToString(p));
            MqClient.Send(msg.ToArray());
        }
        #endregion 

        #region 缓存操作

        #endregion
    }
    #endregion

}
